﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Windows;
using System.Windows.Controls;
using System.Runtime.InteropServices;
namespace Gronia.WPF.Controls
{
    public partial class AlignedWrapPanel : WrapPanel
    {
        #region Overrides

        protected override Size ArrangeOverride(Size finalSize)
        {
            int firstInLine = 0;
            double itemWidth = ItemWidth;
            double itemHeight = ItemHeight;
            double accumulatedV = 0;
            double itemU = (Orientation == Orientation.Horizontal ? itemWidth : itemHeight);
            UVSize curLineSize = new UVSize(Orientation);
            UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height);
            bool itemWidthSet = !DoubleUtilities.IsNaN(itemWidth);
            bool itemHeightSet = !DoubleUtilities.IsNaN(itemHeight);
            bool useItemU = (Orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet);

            List<UIElement> children = new List<UIElement>();
            foreach (UIElement element in InternalChildren)
            {
                if (ChildOrder == ChildOrder.Reverse)
                {
                    children.Insert(0, element);
                    continue;
                }
                children.Add(element);
            }

            for (int i = 0, count = children.Count; i < count; i++)
            {
                UIElement child = children[i] as UIElement;
                if (child == null)
                {
                    continue;
                }
                UVSize sz = new UVSize(Orientation, (itemWidthSet ? itemWidth : child.DesiredSize.Width), (itemHeightSet ? itemHeight : child.DesiredSize.Height));
                if (DoubleUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U))
                {
                    ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU, finalSize);
                    accumulatedV += curLineSize.V;
                    curLineSize = sz;
                    if (DoubleUtilities.GreaterThan(sz.U, uvFinalSize.U))
                    {
                        ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU, finalSize);
                        accumulatedV += sz.V;
                        curLineSize = new UVSize(Orientation);
                    }
                    firstInLine = i;
                }
                else
                {
                    curLineSize.U += sz.U;
                    curLineSize.V = Math.Max(sz.V, curLineSize.V);
                }
            }

            if (firstInLine < children.Count)
            {
                ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU, finalSize);
            }

            return finalSize;
        }

        #endregion

        #region Stuffs
        private struct UVSize
        {
            internal UVSize(Orientation orientation, double width, double height)
            {
                U = V = 0d;
                _orientation = orientation;
                Width = width;
                Height = height;
            }

            internal UVSize(Orientation orientation)
            {
                U = V = 0d;
                _orientation = orientation;
            }

            internal double U;
            internal double V;
            private Orientation _orientation;

            internal double Width
            {
                get { return (_orientation == Orientation.Horizontal ? U : V); }
                set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
            }
            internal double Height
            {
                get { return (_orientation == Orientation.Horizontal ? V : U); }
                set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
            }
        }

        private void ArrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU, Size finalSize)
        {
            double u = 0;
            bool isHorizontal = Orientation == Orientation.Horizontal;
            
            List<UIElement> children = new List<UIElement>();
            foreach (UIElement element in InternalChildren)
            {
                if (ChildOrder == ChildOrder.Reverse)
                {
                    children.Insert(0, element);
                    continue;
                }
                children.Add(element);
            }

            for (int i = start; i < end; i++)
            {
                UIElement child = children[i] as UIElement;
                if (child != null)
                {
                    UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
                    double layoutSlotU = (useItemU ? itemU : childSize.U);
                    double calculatedX = isHorizontal ? u : v;
                    double calculatedY = isHorizontal ? v : u;
                    if (HorizontalContentAlignment == HorizontalAlignment.Right)
                    {
                        calculatedX = finalSize.Width - calculatedX - childSize.U;
                    }

                    if (VerticalContentAlignment == VerticalAlignment.Bottom)
                    {
                        calculatedY = finalSize.Height - calculatedY - childSize.V;
                    }
                    child.Arrange(new Rect(calculatedX, calculatedY, (isHorizontal ? layoutSlotU : lineV), (isHorizontal ? lineV : layoutSlotU)));
                    u += layoutSlotU;
                }
            }
        }
        #endregion
    }
}